// Update.cpp

#include "StdAfx.h"

#include "Update.h"

#include "Common/String/StringConvert.h"

#ifdef _WIN32
#include "Common/System/DLL.h"
#endif

#include "Common/FileSystem/FileDir.h"
#include "Common/FileSystem/FileFind.h"
#include "Common/FileSystem/FileName.h"
#include "Common/FileSystem/EnumDirectory.h"
#include "Common/MS/PropVariant.h"
#include "Common/String/DataConversions.h"
#include "Common/System/Time.h"

#include "Common/Stream/FileStreams.h"

#include "Common/7Zip/CopyCoder.h"

#include "Common/FileSystem/EnumDirectory.h"
#include "OpenArchive.h"
#include "UpdateProduce.h"

#include "Common/Props/SetProperties.h"
#include "Common/FileSystem/TempFiles.h"
#include "UpdateCallback.h"

#include <algorithm>
#include <memory>
#include <vector>

static const char *kUpdateIsNotSupoorted =
  "update operations are not supported for this archive";


static const TCHAR *kTempFolderPrefix = _T("7zE");


namespace Common
{
using namespace System;
using namespace FileSystem;

namespace Archive
{

class ATL_NO_VTABLE COutMultiVolStream:
  public IOutStream,
  public CComObjectRootEx<CComSingleThreadModel>
{
	
  UINT64 _offsetPos; // offset from start of _streamIndex index
  UINT64 _absPos;
  UINT64 _length;

  struct CSubStreamInfo
  {
    COutFileStream			*StreamSpec;
    CComPtr<IOutStream>		Stream;
    CString					Name;
    UINT64					Pos;
    UINT64					RealSize;
	CSubStreamInfo(){}
	CSubStreamInfo(const CSubStreamInfo &obj):
		StreamSpec(obj.StreamSpec),
		Stream(obj.Stream),
		Name(obj.Name),
		Pos(obj.Pos),
		RealSize(obj.RealSize)
	{
	}
	CSubStreamInfo & operator=(const CSubStreamInfo& obj)
	{
		StreamSpec=obj.StreamSpec;
		Stream=obj.Stream;
		Name=obj.Name;
		Pos=obj.Pos;
		RealSize= obj.RealSize;
		return *this;
	}
  };
  typedef  std::vector<CSubStreamInfo> StreamVector;
  StreamVector::size_type _streamIndex; // required stream
  StreamVector Streams;
public:
  // CComPtr<IArchiveUpdateCallback2> VolumeCallback;
	std::vector<UINT64> Sizes;
  CString Prefix;
  CTempFiles *TempFiles;

  void Init()
  {
    _streamIndex = 0;
    _offsetPos = 0;
    _absPos = 0;
    _length = 0;
  }

  HRESULT Close();
  
  BEGIN_COM_MAP(COutMultiVolStream)
		COM_INTERFACE_ENTRY(IOutStream)		
  END_COM_MAP()



	DECLARE_PROTECT_FINAL_CONSTRUCT()

	HRESULT FinalConstruct()
	{
		return S_OK;
	}

	void FinalRelease()
	{
	}

  STDMETHOD(Write)(const void *data, UINT32 size, UINT32 *processedSize);
  STDMETHOD(Seek)(INT64 offset, UINT32 seekOrigin, UINT64 *newPosition);
  STDMETHOD(SetSize)(UINT64 newSize);
};

// static NSynchronization::CCriticalSection g_TempPathsCS;

HRESULT COutMultiVolStream::Close()
{
  HRESULT res = S_OK;
  for (size_t i = 0; i < Streams.size(); i++)
  {
    CSubStreamInfo &s = Streams[i];
    if (s.StreamSpec)
    {
      HRESULT res2 = s.StreamSpec->Close();
      if (res2 != S_OK)
        res = res2;
    }
  }
  return res;
}

STDMETHODIMP COutMultiVolStream::Write(const void *data, UINT32 size, UINT32 *processedSize)
{
  if (processedSize != NULL)
    *processedSize = 0;
  HRESULT hRes (S_OK);
  while(size > 0)
  {
    if (_streamIndex >= Streams.size())
    {
      CSubStreamInfo subStream;

      wchar_t temp[16];
      _ultot(_streamIndex + 1, temp, 10);
      CString res = temp;
      while (res.GetLength() < 3)
        res = CString(_T('0')) + res;
      CString name = Prefix + res;
      subStream.StreamSpec = new CComObject<COutFileStream>;
	  subStream.StreamSpec->FinalConstruct();
      subStream.Stream = subStream.StreamSpec;
      if (!subStream.StreamSpec->Create(name, false))
        return ::GetLastError();
      {
        // NSynchronization::CCriticalSectionLock lock(g_TempPathsCS);
        TempFiles->Paths.push_back(name);
      }

      subStream.Pos = 0;
      subStream.RealSize = 0;
      subStream.Name = name;
	  Streams.push_back(subStream);
      continue;
    }
    CSubStreamInfo &subStream = Streams[_streamIndex];

    auto index = _streamIndex;
    if (index >= Sizes.size())
      index = Sizes.size() - 1;
    UINT64 volSize = Sizes[index];

    if (_offsetPos >= volSize)
    {
      _offsetPos -= volSize;
      _streamIndex++;
      continue;
    }
    if (_offsetPos != subStream.Pos)
    {
      // CComPtr<IOutStream> outStream;
      // RINOK(subStream.Stream.QueryInterface(IID_IOutStream, &outStream));
      hRes = subStream.Stream->Seek(_offsetPos, STREAM_SEEK_SET, NULL);
	  if(S_OK != hRes)
		return hRes;
      subStream.Pos = _offsetPos;
    }

    UINT32 curSize = (UINT32)min((UINT64)size, volSize - subStream.Pos);
    UINT32 realProcessed;
    hRes = subStream.Stream->Write(data, curSize, &realProcessed);
	if(S_OK != hRes)
		return hRes;
    data = (void *)((BYTE *)data + realProcessed);
    size -= realProcessed;
    subStream.Pos += realProcessed;
    _offsetPos += realProcessed;
    _absPos += realProcessed;
    if (_absPos > _length)
      _length = _absPos;
    if (_offsetPos > subStream.RealSize)
      subStream.RealSize = _offsetPos;
    if (processedSize != NULL)
      *processedSize += realProcessed;
    if (subStream.Pos == volSize)
    {
      _streamIndex++;
      _offsetPos = 0;
    }
    if (realProcessed == 0 && curSize != 0)
      return E_FAIL;
    break;
  }
  return S_OK;
}

STDMETHODIMP COutMultiVolStream::Seek(INT64 offset, UINT32 seekOrigin, UINT64 *newPosition)
{
  if (seekOrigin >= 3)
    return STG_E_INVALIDFUNCTION;
  switch(seekOrigin)
  {
    case STREAM_SEEK_SET:
      _absPos = offset;
      break;
    case STREAM_SEEK_CUR:
      _absPos += offset;
      break;
    case STREAM_SEEK_END:
      _absPos = _length + offset;
      break;
  }
  _offsetPos = _absPos;
  if (newPosition != NULL)
    *newPosition = _absPos;
  _streamIndex = 0;
  return S_OK;
}

STDMETHODIMP COutMultiVolStream::SetSize(UINT64 newSize)
{
  size_t i = 0;
  HRESULT hRes (S_OK);
  while (i < Streams.size())
  {
    CSubStreamInfo &subStream = Streams[i++];
    if ((UINT64)newSize < subStream.RealSize)
    {
      hRes = subStream.Stream->SetSize(newSize);
	  if(S_OK != hRes)
		return hRes;
      subStream.RealSize = newSize;
      break;
    }
    newSize -= subStream.RealSize;
  }
  while (i < Streams.size())
  {
    {
      CSubStreamInfo &subStream = Streams.back();
      subStream.Stream.Release();
      FileSystem::DeleteFileAlways(subStream.Name);
    }
	Streams.pop_back();
  }
  _offsetPos = _absPos;
  _streamIndex = 0;
  _length = newSize;
  return S_OK;
}

static const TCHAR *kDefaultArchiveType = _T("7z");
static const TCHAR *kSFXExtension =
  #ifdef _WIN32
    _T("exe");
  #else
    _T("");
  #endif

bool CUpdateOptions::Init(const CCodecs *codecs, const std::vector<int> &formatIndices, const CString &arcPath)
{
  if (formatIndices.size() > 1)
    return false;
  int arcTypeIndex = -1;
  if (formatIndices.size() != 0)
    arcTypeIndex = formatIndices[0];
  if (arcTypeIndex >= 0)
    MethodMode.FormatIndex = arcTypeIndex;
  else
  {
    MethodMode.FormatIndex = codecs->FindFormatForArchiveName(arcPath);
    // It works incorrectly for update command if archive has some non-default extension!
    if (MethodMode.FormatIndex < 0)
      MethodMode.FormatIndex = codecs->FindFormatForArchiveType(kDefaultArchiveType);
  }
  if (MethodMode.FormatIndex < 0)
    return false;
  const CArcInfoEx &arcInfo = codecs->Formats[MethodMode.FormatIndex];
  if (!arcInfo.UpdateEnabled)
    return false;
  CString typeExt = arcInfo.GetMainExt();
  CString ext = typeExt;
  if (SfxMode)
    ext = kSFXExtension;
  ArchivePath.BaseExtension = ext;
  ArchivePath.VolExtension = typeExt;
  ArchivePath.ParseFromPath(arcPath);
  for (size_t i = 0; i < Commands.size(); i++)
  {
    auto &uc = Commands[i];
    uc->ArchivePath.BaseExtension = ext;
    uc->ArchivePath.VolExtension = typeExt;
    uc->ArchivePath.ParseFromPath(uc->UserArchivePath);
  }
  return true;
}

/*
struct CUpdateProduceCallbackImp: public IUpdateProduceCallback
{
  const std::vector<CArcItem> *_arcItems;
  IUpdateCallbackUI *_callback;
  
  CUpdateProduceCallbackImp(const std::vector<CArcItem> *a,
      IUpdateCallbackUI *callback): _arcItems(a), _callback(callback) {}
  virtual HRESULT ShowDeleteFile(int arcIndex);
};

HRESULT CUpdateProduceCallbackImp::ShowDeleteFile(int arcIndex)
{
  return _callback->ShowDeleteFile((*_arcItems)[arcIndex].Name);
}
*/

static HRESULT Compress(
    CCodecs *codecs,
    const Update::CActionSet &actionSet,
    IInArchive *archive,
    const CCompressionMethodMode &compressionMethod,
    CArchivePath &archivePath,
	const std::vector<CArcItem> &arcItems,
    bool shareForWrite,
    bool stdInMode,
    /* const CString & stdInFileName, */
    bool stdOutMode,
	const FileSystem::CDirItems &dirItems,
    bool sfxMode,
    const CString &sfxModule,
	const std::vector<UINT64> &volumesSizes,
    CTempFiles &tempFiles,
    CUpdateErrorInfo &errorInfo,
    IUpdateCallbackUI *callback)
{
  CComPtr<IOutArchive> outArchive;
  HRESULT hRes (S_OK);
  if (archive != NULL)
  {
    CComPtr<IInArchive> archive2 = archive;
    hRes = archive2->QueryInterface(__uuidof(IOutArchive), (void**) &outArchive);
    if (hRes != S_OK)
      throw kUpdateIsNotSupoorted;
  }
  else
  {
    hRes = codecs->CreateOutArchive(compressionMethod.FormatIndex, &outArchive);
	if(S_OK != hRes)
		return hRes;

    #ifdef EXTERNAL_CODECS
    {
      CComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
      outArchive->QueryInterface(__uuidof(ISetCompressCodecsInfo), (void **)&setCompressCodecsInfo);
      if (setCompressCodecsInfo)
      {
        hRes = setCompressCodecsInfo->SetCompressCodecsInfo(codecs);
		if(S_OK != hRes)
			return hRes;
      }
    }
    #endif
  }
  if (outArchive == 0)
    throw kUpdateIsNotSupoorted;
  
  FileTimeType fileTimeType;
  UINT32 value;
  hRes = outArchive->GetFileTimeType(&value);
  if(S_OK != hRes)
	return hRes;

  switch(value)
  {
    case FileTimeType::kWindows:
    case FileTimeType::kUnix:
    case FileTimeType::kDOS:
      fileTimeType = (FileTimeType)value;
      break;
    default:
      return E_FAIL;
  }

  std::vector<CUpdatePair2> updatePairs2;

  {
    std::vector<CUpdatePair> updatePairs;
    GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs); // must be done only once!!!
    // CUpdateProduceCallbackImp upCallback(&arcItems, callback);
    UpdateProduce(updatePairs, actionSet, updatePairs2, NULL /* &upCallback */);
  }

  UINT32 numFiles = 0;
  for (size_t i = 0; i < updatePairs2.size(); i++)
    if (updatePairs2[i].NewData)
      numFiles++;
  
  hRes = callback->SetNumFiles(numFiles);
  if(S_OK != hRes)
	return hRes;

  
  CArchiveUpdateCallback *updateCallbackSpec = new CComObject<CArchiveUpdateCallback>;
  updateCallbackSpec->FinalConstruct();
  CComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);
  
  updateCallbackSpec->ShareForWrite = shareForWrite;
  updateCallbackSpec->StdInMode = stdInMode;
  updateCallbackSpec->Callback = callback;
  updateCallbackSpec->DirItems = &dirItems;
  updateCallbackSpec->ArcItems = &arcItems;
  updateCallbackSpec->UpdatePairs = &updatePairs2;

  CComPtr<ISequentialOutStream> outStream;

  if (!stdOutMode)
  {
    CString resultPath;
    int pos;
    if (!Common::FileSystem::GetFullPathName(archivePath.GetFinalPath(), resultPath, pos))
      throw 1417161;
    Common::FileSystem::CreateComplexDirectory(resultPath.Left(pos));
  }

  COutFileStream *outStreamSpec = NULL;
  COutMultiVolStream *volStreamSpec = NULL;

  if (volumesSizes.size() == 0)
  {
    if (stdOutMode)
	{
		CStdOutFileStream * stream = new CComObject<CStdOutFileStream>;
		stream->FinalConstruct();
		outStream = stream;
	}
    else
    {
      outStreamSpec = new CComObject<COutFileStream>;
	  outStreamSpec->FinalConstruct();
      outStream = outStreamSpec;
      bool isOK = false;
      CString realPath;
      for (int i = 0; i < (1 << 16); i++)
      {
        if (archivePath.Temp)
        {
          if (i > 0)
          {
            wchar_t s[16];
            _ultot(i, s, 10);
            archivePath.TempPostfix = s;
          }
          realPath = archivePath.GetTempPath();
        }
        else
          realPath = archivePath.GetFinalPath();
        if (outStreamSpec->Create(realPath, false))
        {
          tempFiles.Paths.push_back(realPath);
          isOK = true;
          break;
        }
        if (::GetLastError() != ERROR_FILE_EXISTS)
          break;
        if (!archivePath.Temp)
          break;
      }
      if (!isOK)
      {
        errorInfo.SystemError = ::GetLastError();
        errorInfo.FileName = realPath;
        errorInfo.Message = _T("7-Zip cannot open file");
        return E_FAIL;
      }
    }
  }
  else
  {
    if (stdOutMode)
      return E_FAIL;
    volStreamSpec = new CComObject< COutMultiVolStream>;
	volStreamSpec->FinalConstruct();
    outStream = volStreamSpec;
	volStreamSpec->Sizes.assign(volumesSizes.begin(), volumesSizes.end());
    volStreamSpec->Prefix = archivePath.GetFinalPath() + CString(_T("."));
    volStreamSpec->TempFiles = &tempFiles;
    volStreamSpec->Init();

    /*
    updateCallbackSpec->VolumesSizes = volumesSizes;
    updateCallbackSpec->VolName = archivePath.Prefix + archivePath.Name;
    if (!archivePath.VolExtension.IsEmpty())
      updateCallbackSpec->VolExt = CString(_T('.')) + archivePath.VolExtension;
    */
  }

  hRes = SetProperties(outArchive, compressionMethod.Properties);
  if(S_OK != hRes)
	return hRes;

  if (sfxMode)
  {
    CInFileStream *sfxStreamSpec = new CComObject<CInFileStream>;
	sfxStreamSpec->FinalConstruct();
    CComPtr<IInStream> sfxStream(sfxStreamSpec);
    if (!sfxStreamSpec->Open(sfxModule))
    {
      errorInfo.SystemError = ::GetLastError();
      errorInfo.Message = _T("7-Zip cannot open SFX module");
      errorInfo.FileName = sfxModule;
      return E_FAIL;
    }

    CComPtr<ISequentialOutStream> sfxOutStream;
    COutFileStream *outStreamSpec = NULL;
    if (volumesSizes.size() == 0)
      sfxOutStream = outStream;
    else
    {
      outStreamSpec = new CComObject<COutFileStream>;
	  outStreamSpec->FinalConstruct();
      sfxOutStream = outStreamSpec;
      CString realPath = archivePath.GetFinalPath();
      if (!outStreamSpec->Create(realPath, false))
      {
        errorInfo.SystemError = ::GetLastError();
        errorInfo.FileName = realPath;
        errorInfo.Message = _T("7-Zip cannot open file");
        return E_FAIL;
      }
    }
    hRes = Compress::CopyStream(sfxStream, sfxOutStream, NULL);
	if(S_OK != hRes)
		return hRes;
    if (outStreamSpec)
    {
      hRes = outStreamSpec->Close();
	  if(S_OK != hRes)
		return hRes;
    }
  }

  hRes = outArchive->UpdateItems(outStream, updatePairs2.size(), updateCallback);
  callback->Finilize();
  if(S_OK != hRes)
		return hRes;
  if (outStreamSpec)
    hRes = outStreamSpec->Close();
  else if (volStreamSpec)
    hRes = volStreamSpec->Close();
  return hRes;
}

HRESULT EnumerateInArchiveItems(const Wildcard::CCensor &censor,
    const CArc * const pArc,
	std::vector<CArcItem> &arcItems)
{
	ATLASSERT(NULL != pArc);
	arcItems.clear();
  UINT32 numItems;
  IInArchive *archive = pArc->Archive;
  HRESULT hRes = archive->GetNumberOfItems(&numItems);
  if(S_OK != hRes)
	return hRes;
  arcItems.reserve(numItems);
  for (UINT32 i = 0; i < numItems; i++)
  {
    CArcItem ai;

    hRes = pArc->GetItemPath(i, ai.Name);
	if(S_OK != hRes)
		return hRes;
    hRes = IsArchiveItemFolder(archive, i, ai.IsDir);
	if(S_OK != hRes)
		return hRes;
    ai.Censored = censor.CheckPath(ai.Name, !ai.IsDir);
    hRes = pArc->GetItemMTime(i, ai.MTime, ai.MTimeDefined);
	if(S_OK != hRes)
		return hRes;

    {
      CComPropVariant prop;
      hRes = archive->GetProperty(i, kpidSize, &prop);
	  if(S_OK != hRes)
		return hRes;
      ai.SizeDefined = (prop.vt != VT_EMPTY);
      if (ai.SizeDefined)
        ai.Size = String::ConvertPropVariantToUInt64(prop);
    }

    {
      CComPropVariant prop;
      hRes = archive->GetProperty(i, kpidTimeType, &prop);
	  if(S_OK != hRes)
		return hRes;
      if (prop.vt == VT_UI4)
      {
        ai.TimeType = (int)(FileTimeType)prop.ulVal;
        switch(ai.TimeType)
        {
          case FileTimeType::kWindows:
          case FileTimeType::kUnix:
          case FileTimeType::kDOS:
            break;
          default:
            return E_FAIL;
        }
      }
    }

    ai.IndexInServer = i;
	arcItems.push_back(ai);
  }
  return S_OK;
}


static HRESULT UpdateWithItemLists(
    CCodecs *codecs,
    CUpdateOptions &options,
    IInArchive *archive,
	const std::vector<CArcItem> &arcItems,
    CDirItems &dirItems,
    CTempFiles &tempFiles,
    CUpdateErrorInfo &errorInfo,
    IUpdateCallbackUI2 *callback)
{
  HRESULT hRes(S_OK);
  for(size_t i = 0; i < options.Commands.size(); i++)
  {
    auto &command = options.Commands[i];
	
    if (options.StdOutMode)
    {
      hRes = callback->StartArchive(_T("stdout"), archive != 0);
    }
    else
    {
      hRes = callback->StartArchive(command->ArchivePath.GetFinalPath(),
          i == 0 && options.UpdateArchiveItself && archive != 0);
    }
	if(S_OK != hRes)
		return hRes;

    hRes = Compress(
        codecs,
        command->ActionSet, archive,
        options.MethodMode,
        command->ArchivePath,
        arcItems,
        options.OpenShareForWrite,
        options.StdInMode,
        /* options.StdInFileName, */
        options.StdOutMode,
        dirItems,
        options.SfxMode, options.SfxModule,
        options.VolumesSizes,
        tempFiles,
        errorInfo, callback);
	if(S_OK != hRes)
		return hRes;

    hRes = callback->FinishArchive();
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

#if defined(_WIN32) && !defined(UNDER_CE)
class CCurrentDirRestorer
{
  CString _path;
public:
  CCurrentDirRestorer() { Common::FileSystem::GetCurrentDirectory(_path); }
  ~CCurrentDirRestorer() { RestoreDirectory();}
  bool RestoreDirectory() { return BOOLToBool(Common::FileSystem::SetCurrentDirectory(_path)); }
};
#endif

struct CEnumDirItemUpdateCallback: public IEnumDirItemCallback
{
  IUpdateCallbackUI2 *Callback;
  HRESULT ScanProgress(UINT64 numFolders, UINT64 numFiles, const wchar_t *path)
  {
    return Callback->ScanProgress(numFolders, numFiles, path);
  }
};

#ifdef _WIN32
typedef ULONG (FAR PASCAL MY_MAPISENDDOCUMENTS)(
  ULONG_PTR ulUIParam,
  LPSTR lpszDelimChar,
  LPSTR lpszFilePaths,
  LPSTR lpszFileNames,
  ULONG ulReserved
);
typedef MY_MAPISENDDOCUMENTS FAR *MY_LPMAPISENDDOCUMENTS;
#endif

HRESULT UpdateArchive(
    CCodecs *codecs,
    const Wildcard::CCensor &censor,
    CUpdateOptions &options,
    CUpdateErrorInfo &errorInfo,
    IOpenCallbackUI *openCallback,
    IUpdateCallbackUI2 *callback)
{
  if (options.StdOutMode && options.EMailMode)
    return E_FAIL;

  if (options.VolumesSizes.size() > 0 && (options.EMailMode || options.SfxMode))
    return E_NOTIMPL;

  if (options.SfxMode)
  {
	Props::CProperty property(Props::OPTION_SFX, Props::BOOL_TRUE);
	options.MethodMode.Properties.push_back(property);
    if (options.SfxModule.IsEmpty())
    {
      errorInfo.Message = _T("SFX file is not specified");
      return E_FAIL;
    }
    CString name = options.SfxModule;
    #ifdef UNDER_CE
    if (!NFind::DoesFileExist(name))
    #else
    if (!FileSystem::SearchPath(NULL, name, NULL, options.SfxModule))
    #endif
    {
      errorInfo.SystemError = ::GetLastError();
      errorInfo.Message = _T("7-Zip cannot find specified SFX module");
      errorInfo.FileName = name;
      return E_FAIL;
    }
  }


  CArchiveLink arcLink;
  const CString arcPath = options.ArchivePath.GetFinalPath();

  HRESULT hRes (S_OK);

  if (!options.ArchivePath.OriginalPath.IsEmpty())
  {
    FileSystem::File::CFileInfo fi;
    if (fi.Find(arcPath))
    {
      if (fi.IsDir())
        throw "there is no such archive";
      if (options.VolumesSizes.size() > 0)
        return E_NOTIMPL;
      std::vector<int> formatIndices;
      if (options.MethodMode.FormatIndex >= 0)
		  formatIndices.push_back(options.MethodMode.FormatIndex);
      HRESULT result = arcLink.Open2(codecs, formatIndices, false, NULL, arcPath, openCallback);
      if (result == E_ABORT)
        return result;
      hRes = callback->OpenResult(arcPath, result);
	  if(S_OK != hRes)
		return hRes;
      if(S_OK != result)
		  return result;
      if (arcLink.VolumePaths.size() > 1)
      {
        errorInfo.SystemError = (DWORD)E_NOTIMPL;
        errorInfo.Message = _T("Updating for multivolume archives is not implemented");
        return E_NOTIMPL;
      }
      
	  if(!arcLink.Arcs.empty())
	  {
		CArc *pArc  = arcLink.Arcs.back().get();
		pArc->MTimeDefined = !fi.IsDevice;
		pArc->MTime = fi.ModificationTime;
	  }
    }
  }
  else
  {
    /*
    if (archiveType.IsEmpty())
      throw "type of archive is not specified";
    */
  }

  CDirItems dirItems;
  if (options.StdInMode)
  {
	std::shared_ptr<CDirItem> pt = std::make_shared<CDirItem>();
    pt->Name = options.StdInFileName;
	pt->Size = (UINT64)(INT64)-1;
	pt->ResetTimeToCurrent();    
	dirItems.Items.push_back(pt);
  }
  else
  {
    bool needScanning = false;
    for(size_t i = 0; i < options.Commands.size(); i++)
      if (options.Commands[i]->ActionSet.NeedScanning())
        needScanning = true;
    if (needScanning)
    {
      CEnumDirItemUpdateCallback enumCallback;
      enumCallback.Callback = callback;
      hRes = callback->StartScanning();
	  if(S_OK != hRes)
		return hRes;
      CStringVector errorPaths;
      std::vector<DWORD> errorCodes;
      HRESULT res = EnumerateItems(censor, dirItems, &enumCallback, errorPaths, errorCodes);
      for (size_t i = 0; i < errorPaths.size(); i++)
      {
        hRes = callback->CanNotFindError(errorPaths[i], errorCodes[i]);
		if(S_OK != hRes)
			return hRes;
      }
      if (res != S_OK)
      {
        if (res != E_ABORT)
          errorInfo.Message = _T("Scanning error");
        return res;
      }
      hRes = callback->FinishScanning();
	  if(S_OK != hRes)
		return hRes;
    }
  }

  CString tempDirPrefix;
  bool usesTempDir = false;
  
  #ifdef _WIN32
  FileSystem::CTempDirectory tempDirectory;
  if (options.EMailMode && options.EMailRemoveAfter)
  {
    tempDirectory.Create(kTempFolderPrefix);
    tempDirPrefix = tempDirectory.GetPath();
    FileSystem::FileName::NormalizeDirPathPrefix(tempDirPrefix);
    usesTempDir = true;
  }
  #endif

  CTempFiles tempFiles;

  bool createTempFile = false;

  bool thereIsInArchive = arcLink.IsOpen;

  if (!options.StdOutMode && options.UpdateArchiveItself)
  {
    CArchivePath &ap = options.Commands[0]->ArchivePath;
    ap = options.ArchivePath;
    // if ((archive != 0 && !usesTempDir) || !options.WorkingDir.IsEmpty())
    if ((thereIsInArchive || !options.WorkingDir.IsEmpty()) && !usesTempDir && options.VolumesSizes.size() == 0)
    {
      createTempFile = true;
      ap.Temp = true;
      if (!options.WorkingDir.IsEmpty())
      {
        ap.TempPrefix = options.WorkingDir;
        FileSystem::FileName::NormalizeDirPathPrefix(ap.TempPrefix);
      }
    }
  }

  for(size_t i = 0; i < options.Commands.size(); i++)
  {
    CArchivePath &ap = options.Commands[i]->ArchivePath;
    if (usesTempDir)
    {
      // Check it
      ap.Prefix = tempDirPrefix;
      // ap.Temp = true;
      // ap.TempPrefix = tempDirPrefix;
    }
    if (!options.StdOutMode &&
        (i > 0 || !createTempFile))
    {
      const CString &path = ap.GetFinalPath();
      if (FileSystem::Find::DoesFileOrDirExist(path))
      {
        errorInfo.SystemError = 0;
        errorInfo.Message = _T("The file already exists");
        errorInfo.FileName = path;
        return E_FAIL;
      }
    }
  }

  std::vector<CArcItem> arcItems;
  if (thereIsInArchive)
  {
	  hRes = EnumerateInArchiveItems(censor, arcLink.Arcs.back().get(), arcItems);
	if(S_OK != hRes)
		return hRes;
  }

  hRes = UpdateWithItemLists(codecs, options,
      thereIsInArchive ? arcLink.GetArchive() : 0,
      arcItems, dirItems,
      tempFiles, errorInfo, callback);
  if(S_OK != hRes)
	return hRes;

  if (thereIsInArchive)
  {
    hRes = arcLink.Close();
	if(S_OK != hRes)
		return hRes;
    arcLink.Release();
  }

  tempFiles.Paths.clear();
  if (createTempFile)
  {
    try
    {
      CArchivePath &ap = options.Commands[0]->ArchivePath;
      const CString &tempPath = ap.GetTempPath();
      if (thereIsInArchive)
        if (!FileSystem::DeleteFileAlways(arcPath))
        {
          errorInfo.SystemError = ::GetLastError();
          errorInfo.Message = _T("7-Zip cannot delete the file");
          errorInfo.FileName = arcPath;
          return E_FAIL;
        }
      if (!FileSystem::MoveFile(tempPath, arcPath))
      {
        errorInfo.SystemError = ::GetLastError();
        errorInfo.Message = _T("7-Zip cannot move the file");
        errorInfo.FileName = tempPath;
        errorInfo.FileName2 = arcPath;
        return E_FAIL;
      }
    }
    catch(...)
    {
      throw;
    }
  }

  #if defined(_WIN32) && !defined(UNDER_CE)
  if (options.EMailMode)
  {
    DLL::CLibrary mapiLib;
    if (!mapiLib.Load(TEXT("Mapi32.dll")))
    {
      errorInfo.SystemError = ::GetLastError();
      errorInfo.Message = _T("7-Zip cannot load Mapi32.dll");
      return E_FAIL;
    }
    MY_LPMAPISENDDOCUMENTS fnSend = (MY_LPMAPISENDDOCUMENTS)mapiLib.GetProcAddress("MAPISendDocuments");
    if (fnSend == 0)
    {
      errorInfo.SystemError = ::GetLastError();
      errorInfo.Message = _T("7-Zip cannot find MAPISendDocuments function");
      return E_FAIL;
    }
    CStringVector fullPaths;
    size_t i;
    for(i = 0; i < options.Commands.size(); i++)
    {
      CArchivePath &ap = options.Commands[i]->ArchivePath;
      CString arcPath;
      if (!Common::FileSystem::GetFullPathName(ap.GetFinalPath(), arcPath))
      {
        errorInfo.SystemError = ::GetLastError();
        errorInfo.Message = _T("GetFullPathName error");
        return E_FAIL;
      }
      fullPaths.push_back(arcPath);
    }
    CCurrentDirRestorer curDirRestorer;
    for(i = 0; i < fullPaths.size(); i++)
    {
      CString arcPath = fullPaths[i];
      CString fileName = ExtractFileNameFromPath(arcPath);
      CStringA path = String::GetAnsiString(arcPath);
      CStringA name = String::GetAnsiString(fileName);
      // Warning!!! MAPISendDocuments function changes Current directory
      fnSend(0, ";", (LPSTR)(LPCSTR)path, (LPSTR)(LPCSTR)name, 0);
    }
  }
  #endif
  return S_OK;
}

}
}